#
# pluginManager runs the plugins.
#

import sys
import os
import os.path
import runpy
import xml.dom.minidom
from modulefinder import AddPackagePath
import pluginHelper


def out(*args):
	for item in args:
		sys.stderr.write(item)
	sys.stderr.write('\n')


#
# scan for all plugins given a plugin path.
def scanPlugins(path):
	out("Scanning:", path)
	files = os.listdir(path)
	for item in files:
		if item[-3:].lower() == '.py':
			pd = scanPlugin(path,item)
			if pd:
				plugins_loaded[pd.name] = pd

#
# plugin_blacklist is a list of plugins never to load - basically to stop us wasting our time with non-plugin python files (like this one):
plugin_blacklist = ("pluginManager","pluginHelper")

#
# plugins_loaded is a list of all plugins loaded on startup. Other functions interrogate this list and do stuff with the loaded
# plugins, like print their configuration or run them on an input file.
plugins_loaded = {}

#
# scan for a single plugin, given the full path to the .py file and the file name
#
# This function is a bit sneaky since it manages to do all this without corrupting our own global namespace.
def scanPlugin(path, file):
	mod_name = file[:-3]
	if mod_name in plugin_blacklist:
		return

	# attempt the import now:
	out("Looking for plugin %s in path %s" % (mod_name, path))
	#AddPackagePath(mod_name, path)
	if path not in sys.path:
		sys.path.insert(0,path)

	my_globals = runpy.run_module(mod_name)

	if my_globals.has_key("pluginData"):
		pd = my_globals["pluginData"]
		out("Found module named '%s', description: '%s'" % (pd.name, pd.description))
		return pd


def runPlugins(plugins, file):
	plugins = plugins.split(',')

	domImpl = xml.dom.minidom.getDOMImplementation()
	xmlDoc = domImpl.createDocument(None, 'pluginManager', None)

	xmlFile = cxn("inputFile", xmlDoc, file)
	xmlDoc.documentElement.appendChild(xmlFile)

	for pluginName in plugins:
		# does the plugin exist?
		pd = plugins_loaded.get(pluginName)
		if not pd:
			out("plugin %s not found" % (pluginName))
			continue

		# we have the plugin, we need to call the plugin scan function, but we may need to wrap it first.
		#
		# Some scan functions get called once, others need to be called repeatedly:
		if pd.scan_fn_type == pluginHelper.SCANNER_TYPE_FILE_PATH:
			#
			# Call scan function once, with the scan path:
			pd.scan_fn(file)
		elif pd.scan_fn_type == pluginHelper.SCANNER_TYPE_FILE_OBJECT:
			#
			# Call scan function once, with a file-like object.
			#
			# In the future we may stop using a standard python file object, and start using a line-oriented
			# replacement class, since right now this scanner function type really has no use at all.
			pd.scan_fn(open(file,"r"))
		elif pd.scan_fn_type == pluginHelper.SCANNER_TYPE_LINE_BY_LINE:
			#
			# Call the scanner function repeatedly, once for every line in the input file. Stop if it returns False.
			ln = 1
			for line in open(file,"r").readlines():
				if not pd.scan_fn(line, file,ln):
					break
				ln += 1

		if len(pd.results):
			
			xmlPlugin = xmlDoc.createElement("plugin")
			xmlDoc.documentElement.appendChild(xmlPlugin)

			xmlName = xmlDoc.createElement('name')
			xmlNameText = xmlDoc.createTextNode(pluginName)
			xmlName.appendChild(xmlNameText)
			xmlPlugin.appendChild(xmlName)

			
			for item in pd.results:
				xmlResults = cxn("results", xmlDoc)
				xmlPlugin.appendChild(xmlResults)

				result = cxn("result", xmlDoc)
				xmlResults.appendChild(result)

				ls = cxn("lineStart", xmlDoc, str(item[0]))
				result.appendChild(ls)

				col = cxn("colStart", xmlDoc, str(item[1]))
				result.appendChild(col)

				lineSpan = cxn("lineSpan", xmlDoc, str(item[2]))
				result.appendChild(lineSpan)

	print xmlDoc.toprettyxml()
	xmlDoc.unlink()
			


def cxn(name, doc, text=None):
	node = doc.createElement(name)
	if text:
		node.appendChild(doc.createTextNode(text))
	return node

#
# Print the config XML for all plugins in the plugins_loaded dictionary:
def printConfig():
	domImpl = xml.dom.minidom.getDOMImplementation()
	xmlDoc = domImpl.createDocument(None, 'pluginManager', None)

	for name, plugin in plugins_loaded.iteritems():
		xmlPlugin = xmlDoc.createElement('plugin')
		
		xmlName = xmlDoc.createElement('name')
		xmlNameText = xmlDoc.createTextNode(name)
		xmlName.appendChild(xmlNameText)
		xmlPlugin.appendChild(xmlName)

		xmlType = xmlDoc.createElement('type')
		xmlTypeText = xmlDoc.createTextNode(plugin.type)
		xmlType.appendChild(xmlTypeText)
		xmlPlugin.appendChild(xmlType)

		xmlDesc = xmlDoc.createElement('description')
		xmlDescText = xmlDoc.createTextNode(plugin.description)
		xmlDesc.appendChild(xmlDescText)
		xmlPlugin.appendChild(xmlDesc)

		xmlSettings = xmlDoc.createElement('settings')

		for name, (description, default) in plugin.options.iteritems():
			xmlOption = xmlDoc.createElement('option')
			xmlOption.setAttribute('name', name)

			xmlDesc = xmlDoc.createElement('description')
			xmlDescText = xmlDoc.createTextNode(description)
			xmlDesc.appendChild(xmlDescText)
			xmlOption.appendChild(xmlDesc)

			xmlType = xmlDoc.createElement('type')
			strType = 'Unknown'

			if type(default) == type(str()):
				strType = 'String'
			elif type(default) == type(bool()):
				strType = 'Bool'
			elif type(default) == type(int()):
				strType = 'Int'
			elif type(default) == type(list()):
				strType = 'StringList'

			xmlTypeText = xmlDoc.createTextNode(strType)
			xmlType.appendChild(xmlTypeText)
			xmlOption.appendChild(xmlType)

			xmlDefault = xmlDoc.createElement('default')
			xmlDefaultText = xmlDoc.createTextNode(default)
			xmlDefault.appendChild(xmlDefaultText)
			xmlOption.appendChild(xmlDefault)

			xmlSettings.appendChild(xmlOption)

		xmlPlugin.appendChild(xmlSettings)

		xmlDoc.documentElement.appendChild(xmlPlugin)

	print xmlDoc.toprettyxml()
	xmlDoc.unlink()


def printUsage():
	print """Usage: pluginManager.py <action> <action options> <plugin directory>

Possible actions and their options are listed below:

		--printConfig
			Prints the configuration of every plugin found in the plugin
			directory. This action has no options associated with it.

		--runPlugins <plugins> <input> <config file>
			Runs the plugins listed in <plugins> (comma separated list of plugin names),
			on the <input> file.

All actions must specify the plugin directory as the final argument.
"""
	
if __name__ == "__main__":

	if len(sys.argv) < 3:
		printUsage()
		sys.exit(1)
	# plugin directory must always go last:
	pluginDir = sys.argv[-1]
	#
	# do stuff:
	#
	
	if '--printConfig' in sys.argv:
		scanPlugins(pluginDir)
		printConfig()
	elif '--runPlugins' in sys.argv:
		scanPlugins(pluginDir)
		argIndex = sys.argv.index('--runPlugins')
		runPlugins(sys.argv[argIndex +1], sys.argv[argIndex +2])
		
	else:
		printUsage()


		
